/* ===== semantic.c ===== */
#include <string>
#include <iostream>
#include <map>
#include <list>
#include <vector>


using namespace std;

#include <stdio.h>
#include <stdlib.h>
#include "ptype.hh"
#include "symtab.hh"
#include "util.hh"
#include "myASTnode.hh"

#include "semantic.hh"

// feedback the main program with our error status
int TypeError = 0;


/// ---------- Error reporting routines --------------

void errornumparam(int l) {
  TypeError = 1;
  cout<<"L. "<<l<<": The number of parameters in the call do not match."<<endl;
}

void errorincompatibleparam(int l,int n) {
  TypeError = 1;
  cout<<"L. "<<l<<": Parameter "<<n<<" with incompatible types."<<endl;
}

void errorreferenceableparam(int l,int n) {
  TypeError = 1;
  cout<<"L. "<<l<<": Parameter "<<n<<" is expected to be referenceable but it is not."<<endl;
}

void errordeclaredident(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Identifier "<<s<<" already declared."<<endl;
}

void errornondeclaredident(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Identifier "<<s<<" is undeclared."<<endl;
}

void errornonreferenceableleft(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Left expression of assignment is not referenceable."<<endl;
}

void errorincompatibleassignment(int l) {
  TypeError = 1;
  cout<<"L. "<<l<<": Assignment with incompatible types."<<endl;
}

void errorbooleanrequired(int l,string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Instruction "<<s<<" requires a boolean condition."<<endl;
}

void errorisnotprocedure(int l) {
  TypeError = 1;
  cout<<"L. "<<l<<": Operator ( must be applied to a procedure in an instruction."<<endl;
}

void errorisnotfunction(int l) {
  TypeError = 1;
  cout<<"L. "<<l<<": Operator ( must be applied to a function in an expression."<<endl;
}

void errorincompatibleoperator(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Operator "<<s<<" with incompatible types."<<endl;
}

void errorincompatiblereturn(int l) {
  TypeError = 1;
  cout<<"L. "<<l<<": Return with incompatible type."<<endl;
}

void errorreadwriterequirebasic(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Basic type required in "<<s<<"."<<endl;
}

void errornonreferenceableexpression(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Referenceable expression required in "<<s<<"."<<endl;
}

void errornonfielddefined(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Field "<<s<<" is not defined in the struct."<<endl;
}

void errorfielddefined(int l, string s) {
  TypeError = 1;
  cout<<"L. "<<l<<": Field "<<s<<" already defined in the struct."<<endl;
}

/// ------------------------------------------------------------
/// Table to store information about program identifiers
symtab symboltable;

static void InsertintoST(int line,string kind,string id,ptype tp)
{
  infosym p;

  if (symboltable.find(id) && symboltable.jumped_scopes(id)==0) errordeclaredident(line,id);
  else {
    symboltable.createsymbol(id);
    symboltable[id].kind=kind;
    symboltable[id].tp=tp;
  }
}

/// ------------------------------------------------------------

bool isbasickind(string kind) {
  return kind=="int" || kind=="bool";
}


void check_params(AST *a,ptype tp,int line,int numparam)
{
  int callednumparam = 0;
  AST *a1 = child(child(a,1),0);
  while (a1 != 0) {
    ++callednumparam;
    a1 =  a1->right;
  }
   
  if (numparam != callednumparam) errornumparam(line);
  else {
    int i = 1;
    a1 = child(child(a,1),0);
    while (a1 != 0) {
      TypeCheck(a1);
      if(tp->kind=="parref" && !a1->ref) errorreferenceableparam(line,i);
      if(a1->tp->kind != "error" && !equivalent_types(a1->tp, tp->down)) errorincompatibleparam(line,i);
      if(tp) tp = tp->right;
      a1 = a1->right;
      ++i;
    }
  }
}

void insert_vars(AST *a)
{
  if (!a) return;
  TypeCheck(child(a,0));
  InsertintoST(a->line,"idvarlocal",a->text,child(a,0)->tp);
  insert_vars(a->right); 
}

void construct_struct(AST *a)
{
  AST *a1=child(a,0);
  a->tp=create_type("struct",0,0);

  while (a1!=0) {
    TypeCheck(child(a1,0));
    if (a->tp->struct_field.find(a1->text)==a->tp->struct_field.end()) {
      a->tp->struct_field[a1->text]=child(a1,0)->tp;
      a->tp->ids.push_back(a1->text);
     } else {
      errorfielddefined(a1->line,a1->text);
    }
    a1=a1->right;
  }
}

void construct_array(AST *a) {
  AST *a0 =  child(a,0);
  a->tp=create_type("array",0,0);
  
  TypeCheck(child(a,0));	//comprobamos el tamaño ha de ser un int
  TypeCheck(child(a,1));	//comprobamos el tipo del parametro
  
  if(child(a,0)->tp->kind != "error" && child(a,1)->tp->kind != "error" &&
    child(a,1)->tp->kind != "int" && !isbasickind(child(a,1)->tp->kind) &&
    child(a,1)->tp->kind != "struct") { //struct no es un tipo basico
      errorincompatibleoperator(a->line,a->kind);
  }
  else { /*si es un struct entrara por aqui*/
    a->tp->numelemsarray = stringtoint(child(a,0)->text);
    a->tp->down = child(a,1)->tp;
    
  }
}

void construct_procedure(AST *a) {
  a->sc = symboltable.push();
  insert_params(child(child(child(a,0),0),0));
  insert_vars(child(child(a,1),0));
  insert_headers(child(child(a,2),0));
  
  TypeCheck(child(a,2));
  TypeCheck(child(a,3), "instruction");
  
  symboltable.pop();
  
}

void insert_params(AST *a) {

    //Si el procedure o function no tiene parametros
  if(!a) return;
  TypeCheck(child(a,1));
  if (a->kind == "val" || a->kind == "ref") {
    InsertintoST(a->line, "idpar" + a->kind, child(a,0)->text, child(a,1)->tp);    
  }
  insert_params(a->right);
}

void create_params(AST *a, int &nParams) {
  if(!a) return;
  TypeCheck(child(a,1));
  create_params(a->right, ++nParams);
  
  if(a->right) {
    a->tp = create_type("par" + a->kind, child(a,1)->tp, a->right->tp);
  }
  else {
    a->tp = create_type("par" + a->kind, child(a,1)->tp, 0);
  }
}

void create_header(AST *a)
{
  AST *params = child(child(child(a,0),0),0);
  int nParams = 0;
  create_params(params,nParams);
  
  if(a->kind == "procedure") {
    if(params) {
    
      a->tp = create_type(a->kind, params->tp, 0);
    }
    else {
    
      a->tp = create_type(a->kind,0,0);
    }
    a->tp->size = nParams;
  }
  else if (a->kind == "function") {
  
    //tiene retorno?
    AST *ret_a = child(child(a,0),1);
    TypeCheck(ret_a);
    
    if(params) {
    
      a->tp = create_type(a->kind, params->tp, ret_a->tp);
    }
    else {
    
      a->tp = create_type(a->kind, 0, ret_a->tp);
    }
    a->tp->size = nParams;
  }
}


void insert_header(AST *a)
{
  create_header(a);
  if(a->kind == "procedure") {
    InsertintoST(a->line,"idproc", child(a,0)->text, a->tp);
  }
  else {
  
    InsertintoST(a->line, "idunc", child(a,0)->text, a->tp);
  }
}

void insert_headers(AST *a)
{
  while (a!=0) {
    insert_header(a);
    a=a->right;
  }
}


void TypeCheck(AST *a,string info)
{
  if (!a) {
    return;
  }

  //cout<<"Starting with node \""<<a->kind<<"\""<<endl;
  if (a->kind=="program") {
    a->sc=symboltable.push();
    insert_vars(child(child(a,0),0));
    //insertamos las cabeceras de los procedure/functions
    insert_headers(child(child(a,1),0));
    TypeCheck(child(a,1));
    TypeCheck(child(a,2),"instruction");

    symboltable.pop();
  } 
  else if (a->kind=="list") {
    // At this point only instruction, procedures or parameters lists are possible.
    for (AST *a1=a->down;a1!=0;a1=a1->right) {
      TypeCheck(a1,info);
    }
  } 
  else if (a->kind=="ident") {
    if (!symboltable.find(a->text)) {
      errornondeclaredident(a->line, a->text);
    } 
    else {
      a->tp=symboltable[a->text].tp;
      a->ref=1;
    }
  } 
  else if (a->kind=="struct") {
    construct_struct(a);
  }
  else if (a->kind=="array") {
    construct_array(a);  
  }
  else if (a->kind==":=") {
    TypeCheck(child(a,0));
    TypeCheck(child(a,1));
    if (!child(a,0)->ref) {
      errornonreferenceableleft(a->line,child(a,0)->text);
    }
    else if (child(a,0)->tp->kind!="error" && child(a,1)->tp->kind!="error" &&
	     !equivalent_types(child(a,0)->tp,child(a,1)->tp)) {
      errorincompatibleassignment(a->line);
    } 
    else {
      a->tp=child(a,0)->tp;
    }
  } 
  else if (a->kind=="intconst") {
    a->tp=create_type("int",0,0);
  } 
  else if (a->kind=="+" || (a->kind=="-" && child(a,1)!=0) ||
   a->kind=="*" || a->kind=="/") {
    TypeCheck(child(a,0));
    TypeCheck(child(a,1));
    if ((child(a,0)->tp->kind!="error" && child(a,0)->tp->kind!="int") ||
	(child(a,1)->tp->kind!="error" && child(a,1)->tp->kind!="int")) {
      errorincompatibleoperator(a->line,a->kind);
    }
    a->tp=create_type("int",0,0);
  }
  else if (a->kind=="<" || a->kind==">") {
    TypeCheck(child(a,0));
    TypeCheck(child(a,1));
    if ((child(a,0)->tp->kind!="error" && child(a,0)->tp->kind!="int") ||
	(child(a,1)->tp->kind!="error" && child(a,1)->tp->kind!="int")){
      errorincompatibleoperator(a->line,a->kind);
    }
    a->tp=create_type("bool",0,0);  
    
  }
  else if (a->kind=="=") {
    TypeCheck(child(a,0));
    TypeCheck(child(a,1));
    if (!((child(a,0)->tp->kind=="int" && child(a,1)->tp->kind=="int") || 
        (child(a,0)->tp->kind=="bool" && child(a,1)->tp->kind=="bool"))) {
      errorincompatibleoperator(a->line,a->kind);
    }
    a->tp=create_type("bool",0,0);  
 
  }
  else if (a->kind=="-" && child(a,1)==0) {
      TypeCheck(child(a,0));
      if(child(a,0)->tp->kind!="error" && child(a,0)->tp->kind!="int") {
	errorincompatibleoperator(a->line,a->kind);
      }
      a->tp=create_type("int",0,0);
    
  }
  else if (a->kind=="or" || a->kind=="and") {
    TypeCheck(child(a,0));
    TypeCheck(child(a,1));
    
    if (child(a,0)->tp->kind!="error" &&  child(a,0)->tp->kind!="bool" ||
      child(a,1)->tp->kind!="error" && child(a,1)->tp->kind!="bool") {
	errorincompatibleoperator(a->line,a->kind);
    }
    
      a->tp=create_type("bool",0,0);
  }
  else if(a->kind=="not" && child(a,1)==0) {
    TypeCheck(child(a,0));
    if (child(a,0)->tp->kind!="error" && child(a,0)->tp->kind!="bool")  {
      errorincompatibleoperator(a->line,a->kind);
    }
    a->tp=create_type("bool",0,0);
    
  }
  else if (isbasickind(a->kind)) {
    a->tp=create_type(a->kind,0,0);
  }
  else if (a->kind=="writeln") {
    TypeCheck(child(a,0));
    if (child(a,0)->tp->kind!="error" && !isbasickind(child(a,0)->tp->kind)) {
      errorreadwriterequirebasic(a->line,a->kind);
    }
  }
  else if (a->kind==".") {
    TypeCheck(child(a,0));
    a->ref=child(a,0)->ref;
    if (child(a,0)->tp->kind!="error") {
      if (child(a,0)->tp->kind!="struct") {
	errorincompatibleoperator(a->line,"struct.");
      }
      else if (child(a,0)->tp->struct_field.find(child(a,1)->text)==
	       child(a,0)->tp->struct_field.end()) {
	errornonfielddefined(a->line,child(a,1)->text);
      } 
      else {
	a->tp=child(a,0)->tp->struct_field[child(a,1)->text];
      }
    }
  } 
  else if(a->kind=="true"){
    a->tp=create_type("bool",0,0);
  }
  else if(a->kind=="false"){
    a->tp=create_type("bool",0,0);
  }
  else if(a->kind=="if"){
    TypeCheck(child(a,0));
    if(child(a,0)->tp->kind != "error" && child(a,0)->tp->kind != "bool")  {
      errorbooleanrequired(a->line,a->kind);
    }
    //comprobamos los errores de la lista de instrucciones
    TypeCheck(child(a,1),info);
    //Si tenemos else
    if(child(a,2) != 0) {
      TypeCheck(child(a,2),info);
      
    }
  }
  else if(a->kind=="while"){
    TypeCheck(child(a,0));
    if(child(a,0)->tp->kind != "error" && child(a,0)->tp->kind != "bool") {
      errorbooleanrequired(a->line,a->kind);
    }   
        //comprobamos los errores de la lista de instrucciones
    TypeCheck(child(a,1),info);
  }
  else if(a->kind=="[") {
    TypeCheck(child(a,0));
    TypeCheck(child(a,1));
    
    a->ref =  child(a,0)->ref;
    if(child(a,0)->tp->kind != "error") {
      if(child(a,0)->tp->kind != "array") {
	errorincompatibleoperator(a->line,"array[]");
      }
      else {
	a->tp = child(a,0)->tp->down;
      }
    }
    if(child(a,1)->tp->kind != "error" && child(a,1)->tp->kind != "int") {
      errorincompatibleoperator(a->line,"[]");
    }
    
  }
  else if(a->kind=="procedure") {
  
    construct_procedure(a);
  }
  else if (a->kind == "(") {
    
    TypeCheck(child(a,0));
    if (child(a,0)->tp->kind != "error") {
      if(child(a,0)->tp->kind != "procedure" && info == "instruction") {
	errorisnotprocedure(a->line);
      }
      else if (child(a,0)->tp->kind != "function" && info != "instruction") {
	errorisnotfunction(a->line);
      }
    }
       
    if(child(a,0)->tp->kind == "procedure") {
      check_params(a, child(a,0)->tp->down, a->line, child(a,0)->tp->size);
    }
    if (child(a,0)->tp->kind == "function" ) {
      check_params(a, child(a,0)->tp->down, a->line, child(a,0)->tp->size);
      
      a->tp = child(a,0)->tp->right;
    }
  }
  else {
    cout<<"BIG PROBLEM! No case defined for kind "<<a->kind<<endl;
  }
  //cout<<"Ending with node \""<<a->kind<<"\""<<endl;
}
